package com.xbq.lock;

import org.apache.log4j.Logger;

public class RedisLock {

	private static Logger logger = Logger.getLogger(RedisLock.class);
	   /**
	 * 最长时间锁为1天
	 */
	private final static int maxExpireTime = 24 * 60 * 60;
	 
	/**
	 * 系统时间偏移量15秒，服务器间的系统时间差不可以超过15秒,避免由于时间差造成错误的解锁
	 */
	private final static int offsetTime = 15;
	
	 
	/**
	 * 锁只是为了解决小概率事件，最好的方式是不用，从设计上避免分布式锁
	 * 
	 * @param key
	 *            key
	 * @param value
	 * @param waitTime
	 *            秒 - 最大等待时间，如果还无法获取，则直接失败
	 * @param expire
	 *            秒- 锁生命周期时间
	 * @return true 成功 false失败
	 * @throws Exception
	 */
	public static boolean Lock(String key, String value, int waitTime, int expire) {
	 
	    long start = System.currentTimeMillis();
	    String lock_key = key + "_lock";
	    logger.info(Thread.currentThread().getName() + "开始获取分布式锁 key：" + key + " lock_key:" + lock_key + " value:" + value);
	 
	    do {
	        try {
	            Thread.sleep(1);
	            long ret = LockUtil.Setnx(lock_key, System.currentTimeMillis() + "$T$" + value, (expire > maxExpireTime) ? maxExpireTime : expire);
	            if (ret == 1) {
	                logger.info(Thread.currentThread().getName() + "成功获得分布式锁 key：" + key + " value:" + value);
	                return Boolean.TRUE;
	            } else { // 存在锁,并对死锁进行修复
	                String desc = LockUtil.GSetnx(lock_key);
	 
	                // 首次锁检测
	                if (desc.indexOf("$T$") > 0) {
	                    // 上次锁时间
	                    long lastLockTime = Long.valueOf((desc.split("[$T$]")[0]));
	                    // 明确死锁,利用Setex复写，再次设定一个合理的解锁时间让系统正常解锁
	                    if (System.currentTimeMillis() - lastLockTime > (expire + offsetTime) * 1000) {
	                        // 原子操作，只需要一次,【任然会发生小概率事件，多个服务同时发现死锁同时执行此行代码(并发),
	                        // 为什么设置解锁时间为expire（而不是更小的时间），防止在解锁发送错乱造成新锁解锁】
	                        LockUtil.Setex(lock_key, value, expire);
	                        logger.warn(Thread.currentThread().getName() + "发现死锁【" + expire + "秒后解锁】key：" + key + " desc:" + desc);
	                    } else {
	                        logger.info(Thread.currentThread().getName() + "当前锁key：" + key + " desc:" + desc);
	                    }
	                } else {
	                    logger.warn(Thread.currentThread().getName() + "死锁解锁中key：" + key + " desc:" + desc);
	                }
	            }
	            if (waitTime == 0) {
	                break;
	            }
	            Thread.sleep(500);
	        }
	        catch (Exception ex) {
	            logger.error(Thread.currentThread().getName() + "获取锁失败", ex);
	        }
	    }
	    while ((System.currentTimeMillis() - start) < waitTime * 1000);
	    logger.warn(Thread.currentThread().getName() + "获取分布式锁失败 key：" + key + " value:" + value);
	    return Boolean.FALSE;
	}
	 
	/**
	 * 解锁
	 * 
	 * @param key
	 * @return
	 * @throws Exception
	 */
	public static boolean UnLock(String key) {
	    String lock_key = key + "_lock";
	    try {
	        LockUtil.Del(lock_key);
	    }
	    catch (Exception ex) {
	        logger.error("解锁锁失败key：" + key + " lock_key:" + lock_key, ex);
	    }
	    return Boolean.FALSE;
	}
}
